Registry: env constraints (env:emdash, env:astro) end-to-end with admin compatibility warning#1238
Conversation
Plugins published to the experimental registry can declare release-level
environment constraints. A manifest's `requires` block (e.g.
`{ "env:emdash": ">=1.0.0", "env:astro": ">=4.16" }`) is validated at
publish time and written into the release record.
- registry-client: new dependency-free `./env` module shared by the CLI,
server, and admin — `parseRequires` (guards the lexicon-`unknown`
value), `isValidVersionRange`, `satisfiesRange`, and
`checkEnvCompatibility` over a focused semver-range grammar
(comparators, caret, tilde, partial versions, wildcard, AND sets).
- plugin-cli: `RequiresSchema` on the manifest, threaded release-level
into `publishRelease` (never via the profile input); JSON Schema
regenerated.
- core: capture the host Astro version in `astro:config:setup` and
surface it (with EmDash VERSION) on the admin manifest. New
`assertEnvCompatible` gate refuses incompatible install AND update
with `ENV_INCOMPATIBLE` (409), placed after yank-check, before the
artifact fetch.
- admin: RegistryPluginDetail reads host versions, renders a localized
compatibility warning and disables Install when unsatisfied.
Closes #1031.
Share the version->env:* host map between server and admin via a single hostEnvFromVersions helper, dropping the admin's redundant /manifest fetch and the duplicated dev-skip rule (admin now derives host env from the manifest query the shell already runs). Log skipped env constraints server-side (findSkippedEnvConstraints) so a host version the gate can't evaluate is observable rather than a silent bypass. Cover the gate end-to-end through handleRegistryUpdate with a mocked DiscoveryClient, asserting ENV_INCOMPATIBLE aborts before any artifact fetch. Document the accepted, more-permissive prerelease range semantics relative to node-semver.
Replace the hand-rolled range evaluator in registry-client's env module with the semver package. Adds || union support and node-semver prerelease gating; satisfiesRange passes includePrerelease so a prerelease host build is evaluated by precedence rather than excluded from release-only ranges (a prerelease host is not a definite mismatch). isValidVersionRange (shared by the publish-time RequiresSchema) and the fail-open gate semantics are unchanged.
🦋 Changeset detectedLatest commit: 2d4364c The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 2d4364c | May 31 2026, 02:25 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 2d4364c | May 31 2026, 02:26 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 2d4364c | May 31 2026, 02:26 PM |
Scope checkThis PR changes 1,090 lines across 30 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Pull request overview
Adds end-to-end environment constraint enforcement for the experimental plugin registry. Plugins declare release-level requires (e.g. env:emdash, env:astro) with semver ranges in emdash-plugin.jsonc; the CLI validates and publishes them, the admin disables Install with a compatibility warning when the host doesn't satisfy them, and the server refuses install/update with ENV_INCOMPATIBLE (409) as a belt-and-braces gate. Range evaluation is centralized in a shared @emdash-cms/registry-client/env module (uses semver, fail-open on unparseable input, includePrerelease: true). The host's Astro version is newly captured in astro:config:setup.
Changes:
- New shared env-compat module in
registry-client(parse/validate/satisfies + host-env builder + skipped-constraint reporter), wired into the CLI manifest schema and publish pipeline. - Server
assertEnvCompatible+buildHostEnvhelpers used by install and update handlers; newENV_INCOMPATIBLEerror mapped to 409; integration captures host Astro version into manifest/config. - Admin
RegistryPluginDetailreads manifest, evaluatesrequires, shows warning notice, and disables Install on mismatch.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml / pnpm-lock.yaml / packages/registry-client/package.json | Add semver + @types/semver to catalog and registry-client deps |
| packages/registry-client/src/env/index.ts | New shared env-compat helpers (parse, satisfies, host-env, skipped) |
| packages/registry-client/src/index.ts, tsdown.config.ts | Export the new ./env subpath |
| packages/registry-client/tests/env.test.ts | Unit tests for env helpers |
| packages/plugin-cli/src/manifest/schema.ts | RequiresSchema validating env:*/DID keys + semver-range values |
| packages/plugin-cli/src/manifest/translate.ts, commands/publish.ts, publish/api.ts | Thread requires from manifest → publish → release record |
| packages/plugin-cli/schemas/emdash-plugin.schema.json | Published JSON Schema gets the new requires field |
| packages/plugin-cli/tests/manifest-schema.test.ts, manifest-translate.test.ts, publish.test.ts | Tests for new requires plumbing |
| packages/core/src/api/errors.ts | New ENV_INCOMPATIBLE error code mapped to 409 |
| packages/core/src/api/handlers/registry.ts, handlers/index.ts | buildHostEnv, assertEnvCompatible, gating in install + update |
| packages/core/src/astro/integration/index.ts, integration/runtime.ts | Resolve and store host Astro version at astro:config:setup |
| packages/core/src/astro/types.ts, emdash-runtime.ts | Surface astroVersion in manifest |
| packages/core/src/astro/routes/api/admin/plugins/registry/{install,[id]/update}.ts | Pass hostEnv into handlers |
| packages/core/tests/unit/api/registry-env-{compat,gate-handler}.test.ts | Decision-helper + handler-wiring tests |
| packages/admin/src/lib/api/client.ts, registry.ts | astroVersion on AdminManifest, hostEnvFromManifest helper |
| packages/admin/src/components/RegistryPluginDetail.tsx | Compute env mismatches, show warning notice, disable Install |
| .changeset/registry-env-requires.md | Minor bumps for affected packages |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
I reviewed the full diff for this registry environment-constraints feature. The PR introduces requires (release-level env:emdash / env:astro semver constraints) end-to-end: CLI manifest validation, release record publishing, server-side install/update gating, and admin UI compatibility warnings.
What I checked:
- Approach: Sound. The change is additive, backwards-compatible (skips the gate when
hostEnvis absent), and the fail-open semantics for unknown/unparseable versions are well-documented. Using thesemverpackage via a shared@emdash-cms/registry-client/envmodule keeps behavior consistent across CLI, server, and admin. - Security: The gate is defense-in-depth (the admin UI disables Install, and the server rejects with
ENV_INCOMPATIBLE). No SQL changes, no new auth bypasses.parseRequireshardens against garbage shapes and never throws. - Conventions (AGENTS.md): All new admin strings go through Lingui. No physical-directional Tailwind classes (
ml-/mr-/text-left/etc.) were introduced. API errors use the standardApiResultenvelope andmapErrorStatusmapsENV_INCOMPATIBLEto 409. A changeset is present. - Tests: Unit tests cover
assertEnvCompatible,buildHostEnv,checkEnvCompatibility,parseRequires,satisfiesRange, and the manifest schema. An integration test verifies the update handler aborts before artifact fetch when constraints are unsatisfied. - Wiring: The
astroVersionis correctly captured at build time inastro:config:setup, serialized through the virtual module, exposed in the runtime manifest, and consumed by both the install/update routes and the admin detail view.
Headline conclusion: The implementation is clean, well-tested, and consistent with EmDash’s patterns. I found no logic bugs, regressions, security issues, or convention violations.
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
buildHostEnv was a pure pass-through to hostEnvFromVersions (the shared registry-client/env helper). The install/update routes now call hostEnvFromVersions directly, and its duplicate dev-skip/astro-omit test block is removed (covered by registry-client's env tests).
Resolves overlap with the merged #1239 (artifacts): the manifest keeps both release-level `requires` (flat) and the `release.artifacts` block; the publish path writes both; handler barrel exports both registry helpers; manifest-schema tests keep both describe blocks. JSON schema regenerated.
Aligns env constraints with release.artifacts (merged in #1239) under a single release block, matching the RFC. repo stays top-level (documented legacy for backwards compatibility). Publish reads manifest.release.requires; the normalised manifest keeps a flat requires field.
There was a problem hiding this comment.
Re-reviewed this registry environment-constraints feature after prior approval. The PR adds requires (release-level env:emdash / env:astro semver constraints) end-to-end: CLI manifest validation via RequiresSchema, release record publishing, server-side install/update gating via assertEnvCompatible, and admin UI compatibility warnings with disabled Install.
Approach: Still sound. The change is additive, backwards-compatible (the gate skips when hostEnv is absent or a version is unparseable), and the fail-open semantics are well-documented. Using the semver package via a shared @emdash-cms/registry-client/env module keeps behavior consistent across CLI, server, and admin.
What I checked:
- Wiring:
astroVersionis correctly captured at build time inastro:config:setupviaresolveAstroVersion(), serialized through the virtual module, exposed in the runtime manifest (_buildManifest), and consumed by both install/update routes and the admin detail view. - Security: The gate is defense-in-depth (admin UI disables Install, server rejects with
ENV_INCOMPATIBLEmapped to 409). No SQL changes, no new auth bypasses.parseRequireshardens against garbage shapes and never throws. - Conventions (AGENTS.md): All new admin strings go through Lingui (
tmacro /Trans). No physical-directional Tailwind classes (ml-/mr-/text-left/etc.) were introduced. API errors use the standardApiResultenvelope. A changeset is present. - Tests: Unit tests cover
assertEnvCompatible,hostEnvFromVersions,checkEnvCompatibility,parseRequires,satisfiesRange, and the manifest schema. An integration test verifies the update handler aborts before artifact fetch when constraints are unsatisfied. - Edge cases: Empty/dev/non-semver host versions are handled (skipped/fail-open). Prerelease hosts are included via
includePrerelease. DID-keyed constraints are accepted for forward-compat but skipped in env evaluation.
Headline conclusion: The implementation is clean, well-tested, and consistent with EmDash's patterns. No issues were found in this re-review.
What does this PR do?
Adds release-level environment constraints (
requires) to the experimental plugin registry. Plugin authors declareenv:emdash/env:astro(and forward-compat package DIDs) with semver-range values inemdash-plugin.jsonc; the CLI validates and publishes them into the release record. The admin compares them against the running EmDash + Astro versions, shows a compatibility warning, and disables Install when unsatisfied. The server enforces the same check on both install and update, refusing an incompatible release withENV_INCOMPATIBLE(409) so the UI gate can't be bypassed. The host's Astro version — previously not captured anywhere — is now read in the integration'sastro:config:setuphook.Range evaluation uses the
semverpackage, in one shared module used by the publish-time schema, the server gate, and the admin warning. The gate is fail-open (skips constraints it can't evaluate and logs them; malformed ranges are rejected upstream at publish), andsatisfiesRangeusesincludePrereleaseso a prerelease/beta host isn't excluded from release-only ranges.Part of the registry lexicon-to-UI umbrella (#1026).
Closes #1031.
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges included.AI-generated code disclosure
Try this PR
Open a fresh playground →
A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.
Tracks
feat/registry-env-requires. Updated automatically when the playground redeploys.